// Copyright (C) 2011 by Christopher Diggins
// Usage is permitted under the terms of the MIT License 1.0. 	

/** \file 
	Contains implementation of VM not directly responsible for implementing 
	opcodes. 
	\see \ref vm
*/

/** Returns the width of an opcode, that is the number of bytes consumed on the byte-stream
	as constant arguments to the opcode. */
uint vm_op_width(byte op) {
	switch (op)
	{
		#define OPCODE(OP, WIDTH, CAT, DESC) case _##OP: return WIDTH;
		#include "opcodes.h"
		default:
			return 1;
	}
}

/** Collects and removes unreference memory. */
void vm_gc(VM* vm) {
	Val** p;
	MemIter i;

	// Clear the memory flag of all objects
	mem_foreach(&i, &vm->mem)
		val_clear_flag(i.val, VisitedFlag);

	// Visit all objects in the stack.
	for (p = vm->stkbot; p <= vm->sp; ++p)
		mem_val_visit(*p);
	for (p = vm->stktop; p >= vm->ap; --p)
		mem_val_visit(*p);

	// Visit all constants
	{
		Val** begin = array_begin(Val*, &vm->constants);
		Val** end = array_begin(Val*, &vm->constants);
		for (; begin != end; ++begin)
			mem_val_visit(*begin);
	}
	
	// Free dead objects.
	mem_foreach(&i, &vm->mem) {
		if (!val_has_flag(i.val, VisitedFlag))
			mem_val_free(&vm->mem, i.val);
	}
}

/** Pulls the top value converted to an int. */
int vm_pull_int_impl(VM* vm) {
	Val* v = vm_pull();
	int r = val_to_int(v);
	val_del_ref(v);
	return r;
}

/** Duplicates the N top items on the stack. */
void vm_dup_impl(VM* vm, int n) {
	// TODO: could be optimized using memmove.
	// I can also imagine converting a portion of the stack into a list
	// and having a special operation for pushing the entire list.
	int i = 0; for (; i < n; ++i)
		vm_push(val_dup(vm_at(n)));
}

/** Swaps items at two different locations on the stack. */
void vm_swap_impl(VM* vm, int n, int m) {
	Val* tmp = vm_at(n);
	vm_at(n) = vm_at(m);
	vm_at(m) = tmp;
}

/** Moves the top to below the nth value. */
void vm_bury_impl(VM* vm, int n) {
	Val* tmp = vm_top();
	Val** begin = &vm_at(n - 1);
	int size = (n + 1) * sizeof(Val*);
	assert(n >= 0 && n < 256);
	memmove(begin, begin + 1, size); 
	begin[0] = tmp;
}

/** Moves the nth + 1 value to the top. */
void vm_dig_impl(VM* vm, int n) {
	Val* tmp = vm_at(n + 1);
	Val** begin = &vm_at(n + 2);
	int size = (n + 1) * sizeof(Val*);
	assert(n >= 0 && n < 256);
	memmove(begin + 1, begin, size); 
	vm_top() = tmp;
}

/** Implementation of a function call in tail-position. 
	Bound arguments are pushed onto the stack.
	Note: this function removes a reference to 
	the function. */
void vm_tailcall_impl(VM* vm, Val* f) {
	assert(f->type == FunctionType);
	// Push arguments
	if (fun_args(f)) {
		Val** end = fun_arg_begin(f);
		Val** begin = fun_arg_end(f);
		while (end != begin) 
			vm_push(val_dup(*--end));
	}
	vm->ip = fun_code(f);
	val_del_ref(f); 
}

/** Implementation of the while loop. */
void vm_while_impl(VM* vm, Val* f) {
	if (val_to_int(vm_top())) { 
		// Create a function that will point back to the while instruction
		Val* r = fun_new(--vm->ip);
		// We have to add a ref, because making a tail call will cause it to get deleted. 
		f = val_add_ref(f);
		obj_push(r, f);
		// push it to auxiliary function, so that the while function 
		// will execute it when completed. This restores the state to the sane as when
		// the while loop was first called.
		vm_push_aux(r);
		// Call the function 
		vm_tailcall_impl(vm, f);
	}
	else {
		vm_pop();  
		val_del_ref(f);
	}
}

/** Implementation of a function call. */
void vm_call_impl(VM* vm, Val* o) {
	vm_push_aux(fun_new(vm->ip));
	vm_tailcall_impl(vm, o);
}

/** Pushes a value onto the main stack. */
void vm_push_impl(VM* vm, Val* x) {
	*++vm->sp = x;
}

/** Pushes a value onto the auxiliary stack. */ 
void vm_push_aux_impl(VM* vm, Val* x) { 
	*--vm->ap = x;
}

/** Initializes the data structures managed by the VM and allocated memory. */
void vm_init(VM* vm) {
	vm->stkbot = (Val**)calloc(STACK_SIZE, sizeof(vm->stkbot[0]));
	vm->stktop = vm->stkbot + STACK_SIZE - 1;
	vm->ap = vm->stktop + 1;
	vm->sp = vm->stkbot - 1;
	mem_init(&vm->mem);
	array_init(&vm->constants, sizeof(Val*));
	array_init(&vm->functions, sizeof(byte*));
	array_init(&vm->codedata, sizeof(byte));
	vm->ip = NULL;
}

/** Frees all memory associated with the VM. */
void vm_free(VM* vm) {
	mem_free(&vm->mem);
	free(vm->stkbot);
	array_clear(&vm->constants);
	array_clear(&vm->functions);
	array_clear(&vm->codedata);
	vm->stkbot = vm->stktop = vm->ap = vm->sp = NULL;
	vm->ip = NULL;
}

/** Reinitializes all memory associated with the VM. */
void vm_clear(VM* vm) {
	vm_free(vm);
	vm_init(vm);
}

/** Gets a copy of primary stack. */
void vm_get_stack(VM* vm, Val* dest) {
	Val** i = vm->stkbot;
	for (;i <= vm->sp; ++i)
		obj_push(dest, *i);
}

/** Gets a copy of the auxiliary stack. */
void vm_get_aux_stack(VM* vm, Val* dest) {
	Val** i = vm->stktop;
	for (;i >= vm->ap; --i)
		obj_push(dest, *i);
}

/** Sets the code of the VM and initializes the function table. */
void vm_load_code(VM* vm, byte* src, uint size) {
	uint i, j;
	byte* p;
	array_alloc(&vm->codedata, size);
	// We explicitly set the size, because we iterate 
	// over values using a raw pointer.
	array_count(&vm->codedata) = size;
	i = 0;
	p = vm_code_begin();
	while (i < size) {
		// get a pointer the code.
		// Check if this is the begin of a function
		if (i == 0 || src[i-1] == _tail || src[i-1] == _ret)
			// It is so we update the function table.
			array_push(&vm->functions, &p, sizeof(byte));
		// We have to move through arguments in an inner loop 
		// If we did not have this, the above condition 
		// could detect "ret" in an argument.
		for (j = vm_op_width(src[i]); j > 0; --j)
			*p++ = src[i++];
	}
	vm->ip = vm_code_begin();
}

/** Pops all values from the main stack. */
void vm_clear_stack(VM* vm) {
	while (vm->sp >= vm->stkbot) vm_pop();
}

/** Pops all values from the auxiliary stack. */
void vm_clear_aux_stack(VM* vm) {
	while (vm->ap <= vm->stktop) vm_pop_aux();
}

/** Pops all values from both the main and auxiliary stack. */
void vm_clear_stacks(VM* vm) {
	vm_clear_stack(vm);
	vm_clear_aux_stack(vm);
}

/** Executes the next instruction returning a non-zero value if the final "_ret" is reached. */
int vm_eval_next(VM* vm) {
	byte nxt = vm_fetch_byte();
	if ((nxt == _ret) && (vm->ap > vm->stktop)) 
		return 1;
	vm_eval(vm, nxt);
	return 0;
}

/** Executes all instructions loaded in the VM. */
void vm_run(VM* vm) {
	while (!vm_eval_next(vm));
}

/** Restores the object pointed to by "src". */
void vm_restore(VM* vm, Val* src) {
	// TODO:
}

#define FETCH_IMPL(T) T r; memcpy(&r, vm->ip, sizeof(T)); vm->ip += sizeof(r); return r;

/** Retrieves a short from the stream. */
short vm_fetch_short_impl(VM* vm) {
	FETCH_IMPL(short);
}

/** Retrieves an unsigned short from the stream. */
ushort vm_fetch_ushort_impl(VM* vm) {
	FETCH_IMPL(ushort);
}

/** Retrieves a long value from the stream. */
long vm_fetch_long_impl(VM* vm) {
	FETCH_IMPL(long);
}

/** Retrieves an unsigned long from the stream. */
ulong vm_fetch_ulong_impl(VM* vm) {
	FETCH_IMPL(ulong);
}

/** Retrieves a float from the stream. */
float vm_fetch_float_impl(VM* vm) {
	FETCH_IMPL(float);
}

/** Retrieves a double from the stream. */
double vm_fetch_double_impl(VM* vm) {
	FETCH_IMPL(double);
}

/** Throws an exception. */
void vm_throw_impl(VM* vm, Val* x) {
	while (!vm_empty() && vm_top()->type != MarkerType) vm_pop(); vm_pop();
	while (!vm_aux_empty() && vm_top_aux()->type != MarkerType) vm_pop_aux(); vm_pop_aux();
	if (vm_aux_empty()) 
		UNCAUGHT_EXCEPTION(); else 
		vm_call_impl(vm, vm_pull_aux());
}
